﻿using System.Collections.Generic;
using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDFunction
    {
        internal StringName source;
	    internal Variant[] _constants_ptr;
        internal int _constant_count;
	    internal StringName[] _global_names_ptr;
	    internal int _global_names_count;
        internal int[] _default_arg_ptr;
        internal int _default_arg_count;
	    internal int[] _code_ptr;
	    internal int _code_size;
        internal int _argument_count;
        internal int _stack_size;
        internal int _call_size;
        internal int _initial_line;
	    internal bool _static;
        internal GDScript _script;

        internal StringName name;
	    internal List<Variant> _constants;
	    internal List<StringName> _global_names;
	    internal List<int> _default_arguments;

	    internal List<int> _code;

        public GDFunction()
        {
            _stack_size = _call_size = 0;
            name = "<anonymouse>";
        }

        public bool is_static()
        {
            return _static;
        }

        public StringName get_name()
        {
            return name;
        }

        public int[] get_code()
        {
            return _code_ptr;
        }

        public GDScript get_script()
        {
            return _script;
        }

        public int get_code_size()
        {
            return _code_size;
        }

        public int get_max_stack_size()
        {
            return _stack_size;
        }

        public Variant get_constant(int p_idx)
        {
            if (p_idx >= _constants.Count)
                return new Variant("<errconst>");
            return _constants[p_idx];
        }

        public StringName get_global_name(int p_idx)
        {
            if (p_idx >= _global_names.Count)
                return new StringName("<errgname>");
            return _global_names[p_idx];
        }

        public int get_argument_count() 
        { 
            return _argument_count; 
        }

        public int get_default_argument_count()
        {
            return _default_arguments.Count;
        }

        public int get_default_argument_addr(int p_arg)
        {
            if (p_arg >= _default_arguments.Count)
                return -1;
            return _default_arguments[p_arg];
        }

        public Variant call(GDInstance p_instance, Variant[] p_args, ref Variant.CallError r_err)
        {
            if(_code_ptr == null)
                return new Variant();

            r_err.error = Variant.CallError.Error.CALL_OK;

            Variant self = new Variant();
            Variant retvalue = new Variant();
	        Variant[] stack = null;
            Variant[] call_args = null;
	        int defarg = 0;

	        if (p_args.Length != _argument_count) 
            {
		        if (p_args.Length > _argument_count) 
                {
                    r_err.error = Variant.CallError.Error.CALL_ERROR_TOO_MANY_ARGUMENTS;
			        r_err.argument = _argument_count;
			        return new Variant();
		        }
                else if (p_args.Length < _argument_count - _default_arg_count) 
                {
                    r_err.error = Variant.CallError.Error.CALL_ERROR_TOO_FEW_ARGUMENTS;
                    r_err.argument = _argument_count - _default_arg_count;
			        return new Variant();
		        } 
                else 
                {
			        defarg = _argument_count - p_args.Length;
		        }
	        }

            int alloca_size = _call_size + _stack_size;

            if (alloca_size > 0)
            {
                stack = new Variant[alloca_size];

                if (_stack_size > 0)
                {
                    for (int i = 0; i < p_args.Length; i++)
                        stack[i] = new Variant(p_args[i]);

                    for (int i = p_args.Length; i < _stack_size; i++)
                        stack[i] = new Variant();
                }
                else
                {
                    stack = null;
                }

                if (_call_size > 0)
                {
                    call_args = new Variant[_call_size];
                    stack.CopyTo(call_args, _stack_size);
                }
                else
                {
                    call_args = null;
                }
            }
            else
            {
                stack = null;
                call_args = null;
            }

            GDScript class_;
            if (p_instance != null)
            {
                self = p_instance._owner;
                class_ = (GDScript) p_instance.script;
            }
            else
            {
                class_ = _script;
            }

            int ip = 0;
            int line = _initial_line;
            string err_text = "";
            bool exit_ok = false;

            while(ip < _code_size)
            {
                int last_opcode = _code_ptr[ip];

                switch (((GDFunction.Opcode)_code_ptr[ip]))
                {
                    case Opcode.OPCODE_OPERATOR: {
				        bool valid = false;
				        Variant.Operator op = (Variant.Operator)_code_ptr[ip + 1];
				        if(op >= Variant.Operator.OP_MAX)
                            break;

                        Variant a = _get_variant(_code_ptr[ip + 2],p_instance,class_, self, stack, ref err_text);
				        Variant b = _get_variant(_code_ptr[ip + 3],p_instance,class_, self, stack, ref err_text);
				        Variant dst = _get_variant(_code_ptr[ip + 4],p_instance,class_, self, stack, ref err_text);

				        Variant.evaluate(op, a, b, ref dst, ref valid);
				        if (!valid) 
                        {
					        if (false && dst.type == Variant.Type.STRING) 
                            {
						        //return a string when invalid with the error
						        err_text = (string) dst;
					        } 
                            else 
                            {
						        err_text = "Invalid operands '" + Variant.get_type_name(a.type)+ "' and '" + Variant.get_type_name(b.type)+ "' in operator '" + Variant.get_operator_name(op) + "'.";
					        }
					        break;
				        }

				        ip += 5;
                        continue;
                    } break;

                    case Opcode.OPCODE_EXTENDS_TEST: {
                        Variant a = _get_variant(_code_ptr[ip + 1], p_instance, class_, self, stack, ref err_text);
                        Variant b = _get_variant(_code_ptr[ip + 2], p_instance, class_, self, stack, ref err_text);
                        Variant dst = _get_variant(_code_ptr[ip + 3], p_instance, class_, self, stack, ref err_text);

                        Object obj_A = a;
                        Object obj_B = b;

                        GDScript scr_B = (obj_B as GDScript);

				        bool extends_ok = false;

				        if (scr_B != null) 
                        {
					        //if B is a script, the only valid condition is that A has an instance which inherits from the script
					        //in other situation, this shoul return false.

					        if (obj_A.script_instance != null && obj_A.script_instance.language == GDScriptLanguage.singleton) 
                            {

						        GDInstance ins = (obj_A.script_instance as GDInstance);
						        GDScript cmp = ins.script as GDScript;
						        //bool found=false;
						        while(cmp != null) 
                                {
							        if (cmp == scr_B) 
                                    {
								        //inherits from script, all ok
								        extends_ok = true;
								        break;
							        }

							        cmp = cmp._base;
						        }

					        }
				        } 
                        else 
                        {
                        }
                    } break;

                    default: {
                        err_text = "Illegal opcode " + _code_ptr[ip] + " at address " + ip;
                    } break;
                }

                if (exit_ok)
			        break;
		        //error
		        // function, file, line, error, explanation
		        string err_file = "";
		        
                if (p_instance != null)
			        err_file = p_instance._script._path;
		        else if (class_ != null)
			        err_file = class_._path;
		        
                if (err_file == "")
			        err_file = "<built-in>";
		        
                string err_func = name;
		        if (p_instance != null && p_instance.script._name != "")
			        err_func = p_instance.script._name + "." + err_func;
		        
                int err_line = line;
		        
                if (err_text == "") 
                {
			        err_text = "Internal Script Error! - opcode #" + last_opcode + " (report please).";
		        }

	            //if (!GDScriptLanguage.singleton.debug_break(err_text,false)) {
                    // debugger break did not happen

                    //_err_print_error(err_func.utf8().get_data(),err_file.utf8().get_data(),err_line,err_text.utf8().get_data());
                //}


		        break;
            }

            
            //if (ScriptDebugger.singleton != null)
            //    GDScriptLanguage.singleton.exit_function();


	        if (_stack_size != 0) 
            {
		        //free stack
		        //for(int i = 0; i < _stack_size; i++)
			    //    stack[i].~Variant();
	        }

	        return retvalue;
        }
    }
}
